//*********************************************************************************************************************
//  All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
//
//  File name   :	de_tr.c
//
//  Description :	display engine 2.0 transform processing base functions implement
//
//  History     :	2014/04/08  iptang  v0.1  Initial version
//
//*********************************************************************************************************************

#include "transform.h"

#define TR_OFFSET        0x00020000

#define TR_CTL          (0x000)
#define TR_IRQ          (0x004)

#define TR_IN_FMT       (0x020)
#define TR_IN_SIZE      (0x024)

#define TR_IN_PITCH0    (0x030)
#define TR_IN_PITCH1    (0x034)
#define TR_IN_PITCH2    (0x038)

#define TR_IN_LADDR0    (0x040)
#define TR_IN_LADDR1    (0x048)
#define TR_IN_LADDR2    (0x050)

#define TR_IN_HADDR0    (0x044)
#define TR_IN_HADDR1    (0x04c)
#define TR_IN_HADDR2    (0x054)

#define TR_OUT_FMT      (0x080)
#define TR_OUT_SIZE     (0x084)

#define TR_OUT_PITCH0   (0x090)
#define TR_OUT_PITCH1   (0x094)
#define TR_OUT_PITCH2   (0x098)

#define TR_OUT_LADDR0   (0x0a0)
#define TR_OUT_LADDR1   (0x0a8)
#define TR_OUT_LADDR2   (0x0b0)

#define TR_OUT_HADDR0   (0x0a4)
#define TR_OUT_HADDR1   (0x0ac)
#define TR_OUT_HADDR2   (0x0b4)

static uintptr_t tr_base;

#define tr_writel(val, addr) writel(val, (void __iomem *)(addr))
#define tr_readl(addr) readl((void __iomem *)(addr))

int de_tr_set_base(uintptr_t reg_base)
{
	tr_base = reg_base + TR_OFFSET;

	return 0;
}

int de_tr_irq_enable(void)
{
	tr_writel(1<<16, tr_base + TR_IRQ);

	return 0;
}

int de_tr_irq_disable(void)
{
	tr_writel(0, tr_base + TR_IRQ);

	return 0;
}

/* */
int de_tr_irq_query(void)
{
	unsigned int irq_flag;
	unsigned reg_val = 0;

	reg_val = tr_readl(tr_base + TR_IRQ);
	irq_flag = reg_val&0x1;
	if(0x1 == irq_flag) {
		tr_writel(reg_val, tr_base + TR_IRQ);
		return 0;
	}

	return -1;
}

static int de_tr_clk_enable(void)
{
	uintptr_t base = tr_base - TR_OFFSET;
	unsigned int reg_val = 0;
	unsigned int offset = 3;//bit 3

	/* clk div */
	reg_val = tr_readl(base + 0xc);
	reg_val &= ~(0xf << 12);
	reg_val |= (0x0 << 12);
	tr_writel(reg_val, base + 0xc);

	/* reset */
	reg_val = tr_readl(base + 0x8);
	reg_val |= (1<<offset);
	tr_writel(reg_val, base + 0x8);

	/* bus */
	reg_val = tr_readl(base + 0x4);
	reg_val |= (1<<offset);
	tr_writel(reg_val, base + 0x4);

	/* module */
	reg_val = tr_readl(base + 0x0);
	reg_val |= (1<<offset);
	tr_writel(reg_val, base + 0x0);

	return 0;
}

static int de_tr_clk_disable(void)
{
	uintptr_t base = tr_base - TR_OFFSET;
	unsigned int reg_val = 0;
	unsigned int offset = 3;//bit 3

	/* module */
	reg_val = tr_readl(base + 0x0);
	reg_val &= ~(1<<offset);
	tr_writel(reg_val, base + 0x0);

	/* bus */
	reg_val = tr_readl(base + 0x4);
	reg_val &= ~(1<<offset);
	tr_writel(reg_val, base + 0x4);

	/* reset */
	reg_val = tr_readl(base + 0x8);
	reg_val &= ~(1<<offset);
	tr_writel(reg_val, base + 0x8);

	return 0;
}

static int de_tr_set_enable(void)
{
	unsigned int tmp;

	tmp = tr_readl(tr_base + TR_CTL);
	tr_writel((1<<31)|tmp,tr_base + TR_CTL);

	return 0;
}

static int de_tr_set_disable(void)
{
	//when rst need read de top register
	tr_readl(tr_base - TR_OFFSET);
	de_tr_irq_disable();

	return 0;
}

int de_tr_init(void)
{
	return de_tr_clk_enable();
}

int de_tr_exit(void)
{
	de_tr_set_disable();
	de_tr_clk_disable();
	return 0;
}

int de_tr_reset(void)
{
	uintptr_t base = tr_base - TR_OFFSET;
	unsigned int reg_val = 0;
	unsigned int offset = 3;//bit 3

	tr_readl(tr_base - TR_OFFSET);

	reg_val = tr_readl(tr_base + TR_CTL);
	reg_val |= 0x1;//enable bit
	tr_writel(reg_val,tr_base + TR_CTL);

	/* assert */
	reg_val = tr_readl(base + 0x8);
	reg_val &= ~(1<<offset);
	tr_writel(reg_val, base + 0x8);

	udelay(10);//wait for reset finish

	/* de-assert */
	reg_val = tr_readl(base + 0x8);
	reg_val |= (1<<offset);
	tr_writel(reg_val, base + 0x8);

	return 0;
}

int de_tr_exception(void)
{
	mdelay(100);//wait to finish

	return 0;
}

int de_tr_set_cfg(tr_info *info)
{
	int x0,x1,y0,y1;
	unsigned char haddr[3],degree,hflip_en,vflip_en,burst;
	unsigned int w, h, fmt, ycnt = 4, ucnt = 0, tmp, pitch[3] = {0};
	long long addr[3] = {0};

	de_tr_reset();

	//global
	burst = 0x7;
	degree = hflip_en = vflip_en = 0x0;
	if (info->mode<=TR_ROT_270)
	{
		degree = info->mode;
	}
	else if ((info->mode == TR_HFLIP) || (info->mode == TR_HFLIP_ROT_90))
	{
		hflip_en = 0x1;
	}
	else if ((info->mode == TR_VFLIP) || (info->mode == TR_VFLIP_ROT_90))
	{
		vflip_en = 0x1;
	}

	if ((info->mode == TR_HFLIP_ROT_90) || (info->mode == TR_VFLIP_ROT_90))
	{
		degree = 0x1;
	}
	if((info->mode == TR_ROT_0) || (info->mode == TR_ROT_180) || (info->mode == TR_HFLIP) || (info->mode == TR_VFLIP))
	{
		burst = 0x3f;
	}
	tmp = (burst<<16)|(hflip_en<<7)|(vflip_en<<6)|(degree<<4)|(1<<0);
	tr_writel(tmp,tr_base + TR_CTL);

	x0 = x1 = info->src_rect.x;
	y0 = y1 = info->src_rect.y;
	w  = info->src_rect.w;
	h  = info->src_rect.h;

	fmt = info->src_frame.fmt;
	if       (fmt<=TR_FORMAT_BGRX_8888)     {ycnt = 4;}
	else if	 (fmt<=TR_FORMAT_BGR_888)       {ycnt = 3;}
	else if	 (fmt<=TR_FORMAT_BGRA_5551)     {ycnt = 2;}
	else if	 (fmt<=TR_FORMAT_YUV422_I_VYUY) {ycnt = 2;ucnt=0;}
	else if	 (fmt==TR_FORMAT_YUV422_P)      {ycnt = 1;ucnt=1; x1=x0/2;y1=y0;}  //YUV422
	else if	 (fmt==TR_FORMAT_YUV420_P)      {ycnt = 1;ucnt=1; x1=x0/2;y1=y0/2;}//YUV420
	else if	 (fmt==TR_FORMAT_YUV411_P)      {ycnt = 1;ucnt=1; x1=x0/4;y1=y0;}  //YUV411
	else if	 (fmt<=TR_FORMAT_YUV422_SP_VUVU){ycnt = 1;ucnt=2; x1=x0/2;y1=y0;}
	else if	 (fmt<=TR_FORMAT_YUV420_SP_VUVU){ycnt = 1;ucnt=2; x1=x0/2;y1=y0/2;}
	else if	 (fmt<=TR_FORMAT_YUV411_SP_VUVU){ycnt = 1;ucnt=2; x1=x0/4;y1=y0;}
	else                                    {ycnt = 4;}

	pitch[0] = info->src_frame.pitch[0] * ycnt;
	addr[0] = info->src_frame.laddr[0] + pitch[0] * y0 + x0 * ycnt;
	if (fmt >= TR_FORMAT_YUV444_P) {
		pitch[1] = info->src_frame.pitch[1] * ucnt;
		addr[1] = info->src_frame.laddr[1] + pitch[1] * y1 + x1 * ucnt;
	}
	if ((fmt == TR_FORMAT_YUV444_P)
	    || (fmt == TR_FORMAT_YUV422_P)
	    || (fmt == TR_FORMAT_YUV420_P)
	    || (fmt == TR_FORMAT_YUV411_P)) {
		pitch[2] = info->src_frame.pitch[2] * ucnt;
		addr[2] = info->src_frame.laddr[2] + pitch[2] * y1 + x1 * ucnt;
	}

	if((0 != x0) ||(0 != y0)){
		pr_warn("In buffer coordinate is not original point[%d,%d].\n",	x0, y0);
		return -1;
	}

	if((pitch[0]&0xf) ||(pitch[1]&0xf)||(pitch[2]&0xf)){
		pr_warn("In buffer pitch is not 16 byte align[%x,%x,%x].\n",
		pitch[0],pitch[1],pitch[2]);
		return -1;
	}

	haddr[0] = ((addr[0]>>32)&0xff) + info->src_frame.haddr[0];
	haddr[1] = ((addr[1]>>32)&0xff) + info->src_frame.haddr[1];
	haddr[2] = ((addr[2]>>32)&0xff) + info->src_frame.haddr[2];

	if(fmt >= TR_FORMAT_YUV444_I_AYUV) {
		if((addr[0]&0xf) ||(addr[1]&0xf)||(addr[2]&0xf)){
			pr_warn("Input address is not 16 byte align.\n");
			return -1;
		}
	}

	w = w==0?0:w-1;
	h = h==0?0:h-1;
	tr_writel((h<<16)|w,tr_base + TR_IN_SIZE);

	tr_writel(pitch[0],tr_base + TR_IN_PITCH0);
	tr_writel(pitch[1],tr_base + TR_IN_PITCH1);
	tr_writel(pitch[2],tr_base + TR_IN_PITCH2);

	tr_writel(addr[0]&0xffffffff,tr_base + TR_IN_LADDR0);
	tr_writel(addr[1]&0xffffffff,tr_base + TR_IN_LADDR1);
	tr_writel(addr[2]&0xffffffff,tr_base + TR_IN_LADDR2);

	tr_writel(haddr[0],tr_base + TR_IN_HADDR0);
	tr_writel(haddr[1],tr_base + TR_IN_HADDR1);
	tr_writel(haddr[2],tr_base + TR_IN_HADDR2);

	/* dst */
	fmt = info->dst_frame.fmt;
	x0 = info->dst_rect.x;
	y0 = info->dst_rect.y;
	w  = info->dst_rect.w;
	h  = info->dst_rect.h;

	//for ycbcr format output only support yv12
	if(fmt >= TR_FORMAT_YUV444_I_AYUV) {
		ycnt = 1;ucnt=1; x1=x0/2;y1=y0/2;
	}

	pitch[0] = info->dst_frame.pitch[0] * ycnt;
	addr[0] = info->dst_frame.laddr[0] + pitch[0] * y0 + x0 * ycnt;
	if (fmt >= TR_FORMAT_YUV444_P) {
		pitch[1] = info->dst_frame.pitch[1] * ucnt;
		addr[1] = info->dst_frame.laddr[1] + pitch[1] * y1 + x1 * ucnt;
	}
	if ((fmt == TR_FORMAT_YUV444_P)
	    || (fmt == TR_FORMAT_YUV422_P)
	    || (fmt == TR_FORMAT_YUV420_P)
	    || (fmt == TR_FORMAT_YUV411_P)) {
		pitch[2] = info->dst_frame.pitch[2] * ucnt;
		addr[2] = info->dst_frame.laddr[2]+pitch[2]*y1+x1*ucnt;
	}

	if(fmt >= TR_FORMAT_YUV444_I_AYUV) {
		if((pitch[0]&0xf) ||(pitch[1]&0xf)||(pitch[2]&0xf)){
			pr_warn("Out buffer pitch is not 16 byte align[%x,%x,%x].\n",
			pitch[0],pitch[1],pitch[2]);
			return -1;
		}
	}

	if((0 != x0) ||(0 != y0)){
		pr_warn("Out buffer coordinate is not original point[%d,%d].\n", x0, y0);
		return -1;
	}

	haddr[0] = ((addr[0]>>32)&0xff) + info->dst_frame.haddr[0];
	haddr[1] = ((addr[1]>>32)&0xff) + info->dst_frame.haddr[1];
	haddr[2] = ((addr[2]>>32)&0xff) + info->dst_frame.haddr[2];

	if(fmt >= TR_FORMAT_YUV444_I_AYUV) {
		if((addr[0]&0x1f) ||(addr[1]&0x1f)||(addr[2]&0x1f)){
			pr_warn("Output address is not 32 byte align.\n");
			return -1;
		}
	}

	w = w==0?0:w-1;
	h = h==0?0:h-1;
	tr_writel((h<<16)|w,tr_base + TR_OUT_SIZE);

	tr_writel(pitch[0],tr_base + TR_OUT_PITCH0);
	tr_writel(pitch[1],tr_base + TR_OUT_PITCH1);
	tr_writel(pitch[2],tr_base + TR_OUT_PITCH2);

	tr_writel(addr[0]&0xffffffff,tr_base + TR_OUT_LADDR0);
	tr_writel(addr[1]&0xffffffff,tr_base + TR_OUT_LADDR1);
	tr_writel(addr[2]&0xffffffff,tr_base + TR_OUT_LADDR2);

	tr_writel(haddr[0],tr_base + TR_OUT_HADDR0);
	tr_writel(haddr[1],tr_base + TR_OUT_HADDR1);
	tr_writel(haddr[2],tr_base + TR_OUT_HADDR2);

	//src
	if		(TR_FORMAT_YUV422_I_VYUY  == info->src_frame.fmt){fmt = 0x20;}
	else if (TR_FORMAT_YUV422_I_YVYU  == info->src_frame.fmt){fmt = 0x21;}
	else if (TR_FORMAT_YUV422_I_UYVY  == info->src_frame.fmt){fmt = 0x22;}
	else if (TR_FORMAT_YUV422_I_YUYV  == info->src_frame.fmt){fmt = 0x23;}
	else if (TR_FORMAT_YUV422_SP_UVUV == info->src_frame.fmt){fmt = 0x24;}
	else if (TR_FORMAT_YUV422_SP_VUVU == info->src_frame.fmt){fmt = 0x25;}
	else if (TR_FORMAT_YUV422_P       == info->src_frame.fmt){fmt = 0x26;}
	else if (TR_FORMAT_YUV420_SP_UVUV == info->src_frame.fmt){fmt = 0x28;}
	else if (TR_FORMAT_YUV420_SP_VUVU == info->src_frame.fmt){fmt = 0x29;}
	else if (TR_FORMAT_YUV420_P       == info->src_frame.fmt){fmt = 0x2a;}
	else if (TR_FORMAT_YUV411_SP_UVUV == info->src_frame.fmt){fmt = 0x2c;}
	else if (TR_FORMAT_YUV411_SP_VUVU == info->src_frame.fmt){fmt = 0x2d;}
	else if (TR_FORMAT_YUV411_P       == info->src_frame.fmt){fmt = 0x2e;}
	else {fmt = info->src_frame.fmt;}
//	else {fmt = 0x0;}
	tr_writel(fmt,tr_base + TR_IN_FMT);

	de_tr_irq_enable();
	de_tr_set_enable();

	return 0;
}
